package engine

import (
    "github.com/ungerik/go3d/vec3"
    "github.com/ungerik/go3d/vec2"
    "math"
)

type ray struct {
    origin,
    direction vec3.T
}

type raycasting struct {
    ray ray
}

func signf(a float32) float32 {
    b := 0.0
    c := 0.0
    if(0 < a) {
        b = 1
    }
    if(a < 0) {
        c = 1
    }
    return float32(b - c)
}

func stepf(edge, x float32) float32 {
    if(x > edge) {
        return 1.0
    }
    return 0.0
}

func sign(v vec3.T) vec3.T {
    return vec3.T{signf(v[0]), signf(v[1]), signf(v[2])}
}

func step(edge, v vec3.T) vec3.T {
    return vec3.T{stepf(edge[0], v[0]), stepf(edge[1], v[1]), stepf(edge[2], v[2])}
}

func box(r ray, boxSize vec3.T) (vec2.T, vec3.T) {
    rd := r.direction
    ro := r.origin
    m := vec3.T{1 / rd[0], 1 / rd[1], 1 / rd[2]}
    n := m.Muled(&ro)
    mAbs := m.Absed()
    k := mAbs.Muled(&boxSize)
    nInverted := n.Inverted()
    t1 := nInverted.Subed(&k)
    t2 := nInverted.Added(&k)
    tN := math.Max(math.Max(float64(t1[0]), float64(t1[1])), float64(t1[2]));
    tF := math.Min(math.Min(float64(t2[0]), float64(t2[1])), float64(t2[2]));
    if (tN > tF || tF < 0.0) {
        return vec2.T{-1, -1}, vec3.T{0, 0, 0}
    }
    yzx := vec3.T{t1[1], t1[2], t1[0]}
    zxy := vec3.T{t1[2], t1[0], t1[1]}
    v1 := sign(rd)
    v1.Invert()
    v2 := step(yzx, t1)
    v3 := step(zxy, t1)
    outNormal := v1
    outNormal.Mul(&v2)
    outNormal.Mul(&v3)
    return vec2.T{float32(tN), float32(tF)}, outNormal
}

func (r ray) trace(w World) uint {
    size := vec3.T{1, 1, 1}
    minIt := float32(999)
    it := vec2.T{}
    var n vec3.T = vec3.Zero
    for x := uint(0); x < w.Width; x++ {
        for y := uint(0); y < w.Length; y++ {
            for z := uint(0); z < w.Height; z++ {
                cube := w.GetBlock(x, y, z)
                if(cube.Type == 0) { continue }

                cubePos := vec3.T{float32(x), float32(y), float32(z)}
                cubePos.Sub(&r.origin)

                var boxn vec3.T
                it, boxn = box(r, size)
                if(it[0] > 0 && it[0] < minIt) {
                    minIt = it[0]
                    n = boxn
                }
            }
        }
    }
    diff := vec3.Dot(&n, &w.Light)
    return uint(diff * 20)
}

func (rc *raycasting) render(g *Game) {
    g.Screen.window.Clear()
    var uv vec2.T
    for x := uint(0); x < g.Screen.Width(); x++ {
        for y := uint(0); y < g.Screen.Height(); y++ {
            uv = vec2.T{
                (float32(x) / float32(g.Screen.Width()) - 0.5) * 2,
                (float32(y) / float32(g.Screen.Height()) - 0.5) * 2,
            }
            uv[0] *= float32(g.Screen.Width()) / float32(g.Screen.Height())
            r := ray{}
            r.origin = g.World.Camera
            r.direction = vec3.T{1, uv[0], uv[1]}
            r.direction.Normalize()
            col := r.trace(g.World)
            if(col >= uint(len(g.colors))) {
                col = uint(len(g.colors)-1)
            }
            g.Screen.SetChar(int(x), int(y), rune(g.colors[col]))
        }
    }
    g.Screen.Update()
}
